package edu.kit.aifb.ldstream.learner.tasks;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import org.openrdf.model.BNode;
import org.openrdf.model.Statement;
import org.openrdf.model.Value;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.RepositoryResult;
import org.openrdf.rio.ParserConfig;
import org.openrdf.rio.RDFParser.DatatypeHandling;

import edu.kit.aifb.ldstream.env.Environment;
import edu.kit.aifb.ldstream.learner.dao.Instance;
import edu.kit.aifb.ldstream.learner.dao.SimpleStatistics;
import edu.kit.aifb.ldstream.learner.dao.Type;
import edu.kit.aifb.ldstream.stat.util.IDistributionChecker;

public class OutdegreeTask extends LearnerTask {

	private SimpleStatistics stats;
	private Map<Type, ArrayList<Instance>> classMap;
	private IDistributionChecker distribCheck;

	public OutdegreeTask(SimpleStatistics stats, Map<Type, ArrayList<Instance>> classMap, 
			IDistributionChecker distribCheck) {
		this.stats = stats;
		this.classMap = classMap;
		this.distribCheck = distribCheck;
	}

	@Override
	public void run() {
		// first set the overall distribution an count the outdegree for every instance
		if(Environment.VERBOSE){
			System.out.println("Started to learn outdegree.");
		}
		calculateOutdegree();
		// now set the outdegree distribution specifically for every class
		calculateSpecificOutdegree();
		if(Environment.VERBOSE){
			System.out.println("Outdegree learned.");
		}
	}

	private void calculateSpecificOutdegree(){
		Integer[] outdegreeArray;
		int i;
		for(Type t : this.classMap.keySet()){
			i = 0;
			outdegreeArray = new Integer[this.classMap.get(t).size()];
			// collect the outdegrees of every instance
			for(Instance instance : this.classMap.get(t)){
				outdegreeArray[i++] = instance.getOutdegree();
			}
			// set the specific outdegree distribution for type t
			t.setOutdegreeDistribution(this.distribCheck.discrete(outdegreeArray,false));
		}
	}

	private void calculateOutdegree(){
		RepositoryConnection connection = null;

		try {
			connection = Environment.REPO.getConnection();
			connection.setParserConfig(new ParserConfig(true, true, true, DatatypeHandling.VERIFY));
		} catch (RepositoryException e1) {
			e1.printStackTrace();
		}
		int outdegree = 0;
		List<Integer> outdegreeList = new LinkedList<Integer>();
		for(Type rdfClass : this.classMap.keySet()){
			if(Environment.LOGGING){
				Environment.log.info("Countig outdegree for type: " + rdfClass.getURI().stringValue());
			}
			for(Instance instance : this.classMap.get(rdfClass)){

				try {
					RepositoryResult<Statement> result = connection.getStatements(instance.getURI(), null, null , false);
					Statement s;
					Value obj;
					try{
						while(result.hasNext()){
							s = result.next();
							obj = s.getObject();
							if(obj instanceof BNode){
								calculateOutdegree(connection, (BNode) obj, instance);
							}else{
								instance.incrementOutdegree();
							}
						}
					}finally{
						result.close();
					}
				} catch (RepositoryException e) {
					e.printStackTrace();
				}
				outdegree += instance.getOutdegree();
				outdegreeList.add(instance.getOutdegree());
			}
		}
		// outdegreeList.size() is the count of instances
		this.stats.setAverageOutdegree(((double)outdegree) / outdegreeList.size());
		// convert the outdegree list to an array for the distribution check
		Integer[] values = new Integer[outdegreeList.size()];
		values = outdegreeList.toArray(values);
		// check and set the outdegree distribution
		this.stats.setOutdegreeDistribution(this.distribCheck.discrete(values, false));

		try {
			connection.close();
		} catch (RepositoryException e) {
			e.printStackTrace();
		}

	}

	private void calculateOutdegree(RepositoryConnection connection, BNode b, Instance instance){

		try {
			RepositoryResult<Statement> result = connection.getStatements(b, null, null, false);
			try{
				Statement s;
				Value obj;
				while(result.hasNext()){
					s = result.next();
					obj = s.getObject();
					if(obj instanceof BNode){
						calculateOutdegree(connection, (BNode) obj , instance); 
					}else{
						instance.incrementOutdegree();
					}
				}
			}finally{
				result.close();
			}
		} catch (RepositoryException e) {
			e.printStackTrace();
		}



	}

}
